package birdsong.comm;

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import birdsong.comm.ServerComm;
import birdsong.comm.ServerSideMessage;


/**
 * @author Grupo 48
 *
 */
public class MessageFilter implements ServerComm {

	private ServerComm decoratedServerComm;
	private String[]   sensitiveWords;	
	HashMap<String, HashMap<Long, ServerSideMessage>> messageList  = new HashMap<String, HashMap<Long, ServerSideMessage>>();
	HashMap<String, Long> blockList = new HashMap<String, Long>();
	/**
	 * Create the message filter.
	 * 
	 * @param decoratedServerComm the server comm interface to decorate
	 * @param sensitiveWords the words which the filter should check messages for. 
	 *         If a Birdsong message from a client matches any of the words in the array,
	 *         it is rejected.
	 */
	
	public MessageFilter(ServerComm decoratedServerComm, String[] sensitiveWords) {
		this.decoratedServerComm = decoratedServerComm; 
		this.sensitiveWords      = sensitiveWords;
		
	}
	
	
	/**
	 * @see birdsong.comm.ServerComm#start()
	 */
	public void start() {
		decoratedServerComm.start();
	}

	/** 
	 *The getNextMessage is the most important method of the Message Filter.
	 *The Message filter gets messages from the decoratedServerComm and decides what to do.
	 *Only not null messages from unblocked users and without the "censura" criteria are allowed by the filter to post.
	 *All the messages posted in the server are tracked by the filter (user and time) in order to search for repeated messages.
	 *Everytime the user post one message, the filter compares with the previous messages of the last 30 seconds of the user. 
	 *3 equal messages or 10 different messages in 30 seconds of interval = blocked user
	 */
	public ServerSideMessage getNextMessage() {
		
		boolean allow;
		ServerSideMessage message;
		
		do {
			allow   = true;
			message = decoratedServerComm.getNextMessage();
									
			if (message != null && !isBlock(message.getClientNickname())) 
			{
				if(!messageList.containsKey(message.getClientNickname())){
					HashMap<Long, ServerSideMessage> msg = new HashMap<Long, ServerSideMessage>();
					msg.put(new Date().getTime(), message);
					messageList.put(message.getClientNickname(), msg);	
				}else{
					messageList.get(message.getClientNickname()).put(new Date().getTime(), message);
				}

				if (message.getType() == ServerSideMessage.Type.BIRDSONG_MESSAGE) {
					int      index = 0;
					System.out.println("Checking message: " + message.getPayload());
					while (index < sensitiveWords.length && allow) {
						allow = !message.getPayload().contains(sensitiveWords[index++]);
						System.out.println("Checking message: " + message.getPayload() + " againts '" + sensitiveWords[index-1] + "'"  );
												
					}
					if (!allow){
						System.out.println("Message filtered");
						doBlock(message.getClientNickname());
						decoratedServerComm.disconnectClient(message.getClientNickname());
					}
					
				} 
			}
			if(message != null){
				if(isSend3Messages30Sec(message.getClientNickname(), message, new Date()) ||
						isSend10Messages30Sec(message.getClientNickname(), message, new Date())){
					allow = false;
				}
			}
		} while (!allow);

		
		
		return message;
	}



	/** 
	 * @see birdsong.comm.ServerComm#hasNextMessage()
	 */
	@Override
	public boolean hasNextMessage() {
		return decoratedServerComm.hasNextMessage();
	}

	/**
	 * When a client has been disconnected by the spam filter, the client (identified by nickname) should 
	not be allowed to connect again for a period of 30 seconds.
	
	 * Method to verify if the user is blocked or was blocked in the last 30 seconds.
	 * Also if the 30 seconds of block had passed, the user will be removed from the list of blocked users
	 */
	
	private boolean isBlock(String nickname){
		if(!blockList.containsKey(nickname)){
			return false;
		}else{
			Calendar cal = Calendar.getInstance();
			cal.setTime(new Date());
			cal.add(Calendar.SECOND, -30);
			Long timeInMillis = cal.getTimeInMillis();
			
			if(blockList.get(nickname) > timeInMillis){
				return true;
			}else{
				blockList.remove(nickname);
				return false;
			}
			
		}		
	}
	
	/**
	 *Add the user to the list of the blocked uers with the current time
	 */
	private void doBlock(String nickname){
		blockList.put(nickname, new Date().getTime());
	}
	
	/** 
	 * @see birdsong.comm.ServerComm#sendBirdsongMessage(java.lang.String, java.lang.String, java.lang.String)
	 */
	public void sendBirdsongMessage(String fromNicename, String toNickname,
			String birdsongMessage) {
		decoratedServerComm.sendBirdsongMessage(fromNicename, toNickname, birdsongMessage);
	}

	/** 
	 * @see birdsong.comm.ServerComm#sendNewFollower(java.lang.String, java.lang.String)
	 */
	public void sendNewFollower(String toNickname, String newFollower) {
		decoratedServerComm.sendNewFollower(toNickname, newFollower);
	}

	/** 
	 * @see birdsong.comm.ServerComm#sendNewUnfollow(java.lang.String, java.lang.String)
	 */
	public void sendNewUnfollow(String toNickname, String unfollower) {
		decoratedServerComm.sendNewUnfollow(toNickname, unfollower);
		
	}

	/**
	 * @see birdsong.comm.ServerComm#sendError(java.lang.String, java.lang.String)
	 */
	public void sendError(String toNickname, String error) {
		decoratedServerComm.sendError(toNickname, error);
	}

	/** 
	 * @see birdsong.comm.ServerComm#sendClientConnected(java.lang.String, java.lang.String)
	 */
	public void sendClientConnected(String toNickname, String connectedNickname) {
		decoratedServerComm.sendClientConnected(toNickname, connectedNickname);
	}

	/**
	 * @see birdsong.comm.ServerComm#sendClientDisconnected(java.lang.String, java.lang.String)
	 */
	public void sendClientDisconnected(String toNickname,
			String disconnectedNickname) {
		decoratedServerComm.sendClientDisconnected(toNickname, disconnectedNickname);
		
	}

	/** 
	 * @see birdsong.comm.ServerComm#clientIsConnected(java.lang.String)
	 */
	public boolean clientIsConnected(String nickname) {
		return decoratedServerComm.clientIsConnected(nickname);
	}

	/**
	 * @see birdsong.comm.ServerComm#disconnectClient(java.lang.String)
	 */
	public void disconnectClient(String nickname) {
		decoratedServerComm.disconnectClient(nickname);
	}
	
	
	/**
	 2. The spam filter should disconnect users who repeat the message more than three times within 
	 a span of 30 seconds.  A warning should be sent when a user has repeated the same message	twice within the time span.
	 
	 The following method checks if the user had sent 3 times the same messages in less than 30 seconds in x phases:
	 1- Check if the user had already sent any message
	 2- If so, calculate the last 30 seconds of the current time
	 3- Iterate the list of messages and check if the time of the current message is greater or equal to the time of the last 30 seconds.
	 4- All the messages caught on step 3 will be counted and returned to the console when more than 2 are detected.
	 */
	
	private boolean isSend3Messages30Sec(String user, ServerSideMessage message, Date refDate){
		if(!messageList.containsKey(user)){
			return false;
		}else{
			HashMap<Long, ServerSideMessage> list = messageList.get(user);
			int numberMessages = 0;
			Calendar cal = Calendar.getInstance();
			cal.setTime(refDate);
			cal.add(Calendar.SECOND, -30);
			Long timeInMillis = cal.getTimeInMillis();

			Iterator it = list.entrySet().iterator();
			while(it.hasNext()){
				Map.Entry<Long, ServerSideMessage> a = (Map.Entry<Long, ServerSideMessage>)it.next();
				if(a.getKey()>timeInMillis && compareObject(a.getValue(),message)){
					numberMessages+=1;
				}
			}
			if(numberMessages >= 3){
				System.out.println("More than three times within a span of 30 seconds");
				return true;
			}
		}
		return false;
	}
	
	/**
	 The spam filter should disconnect users who send more than 10 messages within a span of 30 
	 seconds. A warning should be sent when a user has sent more than 8 messages the time span.
	 
	 The following method checks if the user had sent 10 messages in less than 30 seconds in x phases:
	 1- Check if the user had already sent any message
	 2- If so, calculate the last 30 seconds of the current time
	 3- Iterate the list of messages and check if the time of the current message is greater or equal to the time of the last 30 seconds.
	 4- All the messages caught on step 3 will be counted and returned to the console when more than 9 are detected.
	 */	

	private boolean isSend10Messages30Sec(String user, ServerSideMessage message, Date refDate){
		if(!messageList.containsKey(user)){
			return false;
		}else{
			HashMap<Long, ServerSideMessage> list = messageList.get(user);
			int numberMessages = 0;
			Calendar cal = Calendar.getInstance();
			cal.setTime(refDate);
			cal.add(Calendar.SECOND, -30);
			Long timeInMillis = cal.getTimeInMillis();

			Iterator it = list.entrySet().iterator();
			while(it.hasNext()){
				Map.Entry<Long, ServerSideMessage> a = (Map.Entry<Long, ServerSideMessage>)it.next();
				if(a.getKey()>timeInMillis){
					numberMessages+=1;
				}
			}
			
			if(numberMessages >= 10){
				System.out.println("more than 10 messages within a span of 30 seconds");
				return true;
			}
		}
		return false;
	}
	
	
	/**
	 Auxiliar method to compare messages.
	 Check's if they are null, payload and type of message.
	 */		
	
	// Aux method
	private boolean compareObject(ServerSideMessage a, ServerSideMessage b){
		if(a == null && b == null){
			return true;
		}else if (a != null && b == null || a == null && b != null){
			return false;
		} else{
			
			return a.getPayload().equals(b.getPayload()) && 			
			a.getType().equals(b.getType());
		}
	}
}
